#! /usr/bin/env python

from ircbot import SingleServerIRCBot
from irclib import nm_to_n, nm_to_h, irc_lower, ip_numstr_to_quad, ip_quad_to_numstr, ServerConnectionError
import random, time, re

class PickupBot(SingleServerIRCBot):
    def get_version(self):
        return "PyPickupBot 0.1.1 <https://launchpad.net/pypickupbot>"

    def __init__(self, configfile):
        self.cfgLoaded = False

        self.configFile = configfile
        self._reload_config()

        SingleServerIRCBot.__init__(self, [(self.cfg['channel']['server'], self.cfg['channel']['port'])], self.cfg['channel']['nick'], self.cfg['channel']['realname'])

        random.seed()

        self.topic = self.cfg['info']['defaultTopic']
        self.nextTopic = ""
        self.nextTopicDefinedAt = time.time()
        self.lastPromote = 0
        self.lastGames = {}

    def _reload_config(self):
        config = {}
        execfile(self.configFile, globals(), config)


        if self.cfgLoaded:
            old = {}
            old['mainchannel'] = self.mainchannel
            old['otherchannels'] = self.otherchannels

        self.cfg = config['config']

        self.mainchannel = self.cfg['channel']['mainchannel']
        self.otherchannels = self.cfg['channel']['otherchannels']
        
        if self.cfgLoaded and ( self.mainchannel != old['mainchannel'] or self.otherchannels != old['otherchannels'] ):
            self._joinChannels()

        self.messages = self.cfg['messages']
        self.playableGames = self.cfg['games']
        self.rulesURL = self.cfg['info']['rulesURL']

        if not self.cfgLoaded:
            self.games = {}

        for game in self.playableGames:
            if game not in self.games:
                self.games[game] = []
            elif len( self.games[game] ) > self.playableGames[game][1]:
                self.games[game] = []
            elif len( self.games[game] ) == self.playableGames[game][1]:
                self._checkGame( game )
        
        gamesToRemove = []

        for game in self.games:
            if game not in self.playableGames:
                gamesToRemove.append( game )

        for game in gamesToRemove:
            del self.games[game]

        if self.cfgLoaded:
            self.updateTopic()

        self.cfgLoaded = True

    def _reload(self, nick, args):
        if not self.needs_admin(nick):
            return
        self._reload_config()

    # Overloaded _connect to allow a password
    def _connect(self):
        """[Internal]"""
        password = self.cfg['channel']['password']
        if len(self.server_list[0]) > 2:
            password = self.server_list[0][2]
        try:
            self.connect(self.server_list[0][0],
                         self.server_list[0][1],
                         self._nickname,
                         password,
                         ircname=self._realname)
        except ServerConnectionError:
            pass

    def on_welcome(self, c, e):
        self._joinChannels()

    def _joinChannels(self):
        c = self.connection
        channelsToJoin = []
        channelsToPart = []


        for channel in [self.mainchannel] + self.otherchannels:
            if channel not in self.channels:
                channelsToJoin.append( channel )

        for channel in self.channels:
            if channel != self.mainchannel and channel not in self.otherchannels:
                channelsToPart.append( channel )

        if len( channelsToPart ) > 0:
            c.part( channelsToPart )

        if len( channelsToJoin ) > 0:
            c.join( ','.join( channelsToJoin ) )

    def on_nicknameinuse(self, c, e):
        c.nick(c.get_nickname() + "_")

    def on_privmsg(self, c, e):
        self.do_command(e, e.arguments()[0])

    def on_pubmsg(self, c, e):
        a = e.arguments()[0].split(":", 1)
        if len(a) > 1 and irc_lower(a[0]) == irc_lower(self.connection.get_nickname()):
            if e.target() != self.mainchannel:
                c.notice(nm_to_n( e.source() ), 'Please only issue commands in %s.' % self.mainchannel)            
                return
            self.do_command(e, a[1].strip())
        elif e.arguments()[0].startswith('!'):
            if e.target() != self.mainchannel:
                c.notice(nm_to_n( e.source() ), 'Please only issue commands in %s.' % self.mainchannel)            
                return
            self.do_command(e, e.arguments()[0].lstrip('!').strip())
        return

    def on_mode(self, c, e):
        if e.target() == self.mainchannel and e.arguments() == ['+o', c.real_nickname]:
            self.updateTopic()

    def _is_admin(self, nick):
        return self.channels[self.mainchannel].is_oper(nick)

    def needs_admin(self, nick):
        if not self._is_admin(nick):
            self.connection.notice(nick, 'You cannot issue this command.')
            return False
        return True

    def do_command(self, e, cmdline):
        nick = nm_to_n(e.source())
        c = self.connection
        args = cmdline.split(' ')
        cmd = args[0].lower()

        if re.match("^[! ]*$", cmdline):
            return

        if e.target() != self.mainchannel and not self._is_admin(nick):
            c.notice(nick, 'Please only issue commands in %s.' % self.mainchannel)
            return

        if cmd == "quit":
            if self.needs_admin( nick ):
                self.die()
        elif cmd == "add":
            self._handleAdd(nick, args)
        elif cmd == "remove":
            self._handleRemove(nick, args)
        elif cmd == "pull":
            self._pull(nick, args)
        elif cmd == "promote":
            self._promote(nick, args)
        elif cmd == "who":
            self.who(nick, args)
        elif cmd == "lastgame":
            self.lastGame(nick, args)
        elif cmd == "start":
            self._start(nick, args)
        elif cmd == "topic":
            self._topic(nick, args)
        elif cmd == "reload":
            self._reload(nick, args)
        elif cmd == "help":
            c.notice(nick, "See %s for help." % (self.rulesURL))
        else:
            c.notice(nick, "Not understood: " + cmd)

    def _handleAdd(self, nick, args):
        c = self.connection
        if len(args) < 2:
            c.notice( nick, 'Syntax is: !add <game> [<game> [ ... ]], <game> being one of: %s.' % ', '.join( self.playableGames.keys() ) )
            return
        
        games = []
        passedCmd = False
        for g in args:
            if not passedCmd:
                passedCmd = True
                continue
            game = g.lower()
            if game not in self.games:
                c.notice( nick, 'Unknown game %s. Playable games are: %s.' % (game , ', '.join (self.playableGames.keys() ) ) )
                return
            if nick in self.games[game]:
                c.notice( nick, 'Already in game %s.' % (game) )
                return
            if game in games:
                c.notice( nick, 'You can\'t sign up for the same game multiple times.' )
                return

            games.append(game)

        for game in games:
            self.games[game].append( nick )
            self._checkGame( game )

        self.updateTopic()

    def _handleRemove(self, nick, args):
        c = self.connection
        games = self._selectGames( nick, args, 1 )

        if games == -1:
            return

        for game in games:
            if nick not in self.games[game]:
                continue
            self.games[game].remove( nick )

        self.updateTopic()

    def _pull(self, nick, args):
        if not self.needs_admin(nick):
            return
        if len( args ) < 2:
            self.connection.notice( nick, 'Syntax is: !pull <player> <games>')
            return
        games = self._selectGames( nick, args, 2 )
        if games == -1:
            return
        for game in games:
            if args[1] not in self.games[game]:
                continue
            self.games[game].remove( args[1] )

        self.updateTopic()

    def updateTopic(self):
        games = []
        for game in self.games:
            games.append( self.messages['topic_game'] % { 'gamename': game, 'playernum': len( self.games[game] ), 'playermax': self.playableGames[game][1] } )
        self._setTopic( self.messages['topic'] % { 'games': self.messages['topic_game_join'].join(games), 'rulesURL': self.rulesURL, 'topic': self.topic, 'title': self.cfg['info']['title'] } )

    def _setTopic(self, newTopic):
        """Schedules a time for setting a new topic to avoid flooding."""
        self.nextTopic = newTopic
        self.nextTopicDefinedAt = time.time()
        self.connection.execute_delayed( 2, self._setTopicNow, [self.nextTopicDefinedAt] )

    def _setTopicNow(self, nextTopicDefinedAt):
        """Tries to set the topic if _setTopic hasn't been called since nextTopicDefinedAt"""
        if self.nextTopicDefinedAt == nextTopicDefinedAt:
            self.connection.topic( self.mainchannel, self.nextTopic )

    def _topic(self, nick, args):
        if not self.needs_admin(nick):
            return
        args.pop(0)
        if len( args ) == 0:
            self.connection.notice( nick, self.topic )
        else:
            self.topic = ' '.join( args )
            self.updateTopic()

    def _checkGame(self, game):
        if self.playableGames[game][1] == len( self.games[game] ):
            self._startGame( game )

    def _start(self, nick, args):
        c = self.connection
        if not self.needs_admin(nick):
            return
        if len(args) < 2 or args[1] not in self.games:
            c.notice( nick, 'Syntax is: !start <game> (<games> being one or more of: %s.)' % ' '.join( self.playableGames.keys() ) )
            return

        if len( self.games[args[1]] ) < self.playableGames[args[1]][2]:
            c.notice( nick, 'Not enough players to choose captains from.' )
            return

        if len( self.games[args[1]] ) < 1:
            c.notice( nick, 'Can\'t start empty games.' )
            return

        self._startGame(args[1])

    def _selectGames(self, nick, args, offset):
        if len(args) <= offset:
            games = self.playableGames.keys()
#            c.privmsg( nick, 'Syntax is: !remove [<game> [<game> [ ... ]]], <game> being one of: %s.' % ', '.join( self.playableGames.keys() ) )
#            return
        else:
            games = []
            i = 0
            for g in args:
                if i < offset:
                    i += 1
                    continue
                game = g.lower()
                if game in games:
                    continue
                if game not in self.games:
                    self.connection.notice( nick, 'Unknown game %s. Possible games are: %s.' % (game , ' '.join (self.playableGames.keys() ) ) )
                    return -1
                
                games.append( game )

        return games

    def _startGame(self, game):
        c = self.connection

        players = self.games[game]

        if len( players ) < self.playableGames[game][2]:
            print 'ERROR: Game %s has less players than captains' % game
            c.privmsg(self.mainchannel, 'Something went wrong..')
            return

        seeds = []
        
        while self.playableGames[game][2] > len ( seeds ):
            seed = random.randint( 0, len( self.games[game] )-1 )
            if seed not in seeds:
                seeds.append( seed )

        normalplayers = []
        captains = []

        for seed in seeds:
            captains.append( players[seed] )

        for player in players:
            for othergame in self.games:
                if othergame == game:
                    continue
                if player in self.games[othergame]:
                    self.games[othergame].remove( player )
            if player not in captains:
                normalplayers.append( player )
        
        c.notice( self.mainchannel, 'Game ready to start in %s!' % ( self.mainchannel ) )


        if len( captains ) > 0:
            msg = self.messages['game_ready'] % {
                'gamename': self.playableGames[game][0],
                'playerlist': ', '.join(players),
                'captainlist': ', '.join(captains)
                }

            c.privmsg( self.mainchannel, msg )
            self.lastGames[game] = ( time.time(), msg )
        else:
            msg = self.messages['game_ready_nocaptains'] % {
                'gamename': self.playableGames[game][0],
                'playerlist': ', '.join(players),
                }
            c.privmsg( self.mainchannel, msg )
            self.lastGames[game] = ( time.time(), msg )

        c.privmsg_many( captains, 'Game ready to start in %s! You\'re a captain!' % ( self.mainchannel ) )

        self.games[game] = []
        self.updateTopic()

    def who(self, nick, args):
        c = self.connection
        
        games = self._selectGames(nick, args, 1)
        if games == -1:
            return;

        who = []

        for game in games:
            if len( self.games[game] ) == 0:
                continue;
            who.append( self.messages['who_game'] % { 'gamename': game, 'playernum': len( self.games[game] ), 'playermax': self.playableGames[game][1], 'playerlist': ', '.join(self.games[game]) } )
        
        if len( who ) == 0 and len( games ) == len( self.games ):
            c.notice( nick, self.messages['who_nothingatall'] )
        elif len( who ) == 0:
            c.notice( nick, self.messages['who_nothing'] % { 'games': ' '.join( games ) } )
        elif len( games ) == len( self.games ):
            c.notice( nick, self.messages['who_all'] % { 'games': self.messages['who_game_join'].join( who ) } )
        else:
            c.notice( nick, self.messages['who'] % { 'games': self.messages['who_game_join'].join( who ) } )

    def _promote(self, nick, args):
        c = self.connection
        t = time.time()
        if len( args ) != 2 or args[1] not in self.games:
            c.notice( nick, 'Syntax: !promote <game>, <game> being one of: %s.' % ', '.join( self.playableGames.keys() ) )
            return

        admin = self._is_admin(nick)
        if nick not in self.games[args[1]] and not admin:
            c.notice( nick, 'You haven\'t even added yourself! Show the example and type !add %s first!' % args[1] )
            return
        if self.lastPromote + self.cfg['channel']['promotedelay'] > t and not admin:
            c.notice( nick, 'Can\'t promote so often.' )
            return
        self.lastPromote = t
        
        msg = "\x02Please !add up for %s in %s ! %i\x02 more people needed! (Type \x02!add %s\x02 in %s)" % (
                                      self.playableGames[args[1]][0],
                                            self.mainchannel,
                                                 self.playableGames[args[1]][1] - len( self.games[args[1]] ),
                                                                                           args[1],
                                                                                                     self.mainchannel )
        if len ( self.otherchannels ) > 0:
            c.privmsg_many( self.otherchannels, msg )
        c.notice( self.mainchannel, msg )

    def lastGame(self, nick, args):
        if len(self.lastGames) < 1:
            self.connection.notice( nick, "No game played yet." )
            return
        if len(args) == 1:
            game = ("", 0,"")
            for (k, (t, v)) in self.lastGames.iteritems():
                if t > game[1]:
                    game = (k, t, v)
        elif len(args) == 2:
            games = self._selectGames( nick, args, 1 )
            if games == -1:
                return
            if games[0] not in self.lastGames:
                self.connection.notice( nick, "No %s game played yet." % self.playableGames[games[0]][0] )
                return
            (t, v) = self.lastGames[games[0]]
            game = ( games[0], t, v )
        else:
            self.connection.notice( nick, "One game at a time please. :)" )
            return
        
        now = time.time()
        age = int(now - game[1])

        (days, rest) = divmod( age, 60*60*24 )
        (hours, rest) = divmod( age, 60*60 )
        (minutes, seconds) = divmod( rest, 60 )
        
        timestr = ""
        if days > 0:
            if days == 1:
                timestr += str( days ) + " day"
            else:
                timestr += str( days ) + " days"

        if hours > 0:
            if len(timestr) > 0:
                timestr += ", "
            if hours == 1:
                timestr += str( hours ) + " hour"
            else:
                timestr += str( hours ) + " hours"

        if minutes > 0:
            if len(timestr) > 0:
                timestr += ", "
            if minutes == 1:
                timestr += str(minutes) + " minute"
            else:
                timestr += str(minutes) + " minutes"

        if len(timestr) < 1:
            timestr += "About right now"
        else:
            timestr += " ago"

        self.connection.notice( nick, "%(age)s: %(game)s" % { 'age': timestr, 'game': game[2] } )


import pypickupbot
from optparse import OptionParser

def main():
    parser = OptionParser("Usage: %prog configfile")
    (options, args) = parser.parse_args()
    if len( args ) != 1:
        parser.error("incorrect number of arguments")
    
    configfile = args[0]

    bot = PickupBot( configfile )
    bot.start()

if __name__ == "__main__":
    main()

